#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>


union OptVal {
	int ival;
	struct linger linvar;
	struct timeval tmvar;
};


static char g_buf[4096];

static const char*
sock_str_flag(union OptVal *var, int len) {
	if (sizeof(int) != len) {
		snprintf(g_buf, sizeof(g_buf), "invalid len for flag");
	} else {
		snprintf(g_buf, sizeof(g_buf), "%s", var->ival ? "on" : "off");
	}
	return g_buf;
}

static const char*
sock_str_int(union OptVal *var, int len) {
	if (sizeof(int) != len) {
		snprintf(g_buf, sizeof(g_buf), "invalid len for int");
	} else {
		snprintf(g_buf, sizeof(g_buf), "%d", var->ival);
	}
	return g_buf;
}

static const char*
sock_str_linger(union OptVal *var, int len) {
	if (sizeof(struct linger) != len) {
		snprintf(g_buf, sizeof(g_buf), "invalid len for linger");
	} else {
		snprintf(g_buf, sizeof(g_buf), "l_onoff:%d l_linger:%d", var->linvar.l_onoff, var->linvar.l_linger);
	}
	return g_buf;	
}

static const char*
sock_str_timeval(union OptVal *var, int len) {
	if (sizeof(struct timeval) != len) {
		snprintf(g_buf, sizeof(g_buf), "invalid len for timeval");
	} else {
		snprintf(g_buf, sizeof(g_buf), "tv_sec:%ld tv_usec:%ld", (long)var->tmvar.tv_sec, (long)var->tmvar.tv_usec);
	}
	return g_buf;
}


struct SockOpts {
  const char *opt_str;
  int opt_level;
  int opt_name;
  const char* (*opt_val_str)(union OptVal*, int);
} sock_opts[] = {
	{ "SO_BROADCAST",		SOL_SOCKET,	SO_BROADCAST,	sock_str_flag },
	{ "SO_DEBUG",			SOL_SOCKET,	SO_DEBUG,		sock_str_flag },
	{ "SO_DONTROUTE",		SOL_SOCKET,	SO_DONTROUTE,	sock_str_flag },
	{ "SO_ERROR",			SOL_SOCKET,	SO_ERROR,		sock_str_int },
	{ "SO_KEEPALIVE",		SOL_SOCKET,	SO_KEEPALIVE,	sock_str_flag },
	{ "SO_LINGER",			SOL_SOCKET,	SO_LINGER,		sock_str_linger },
	{ "SO_OOBINLINE",		SOL_SOCKET,	SO_OOBINLINE,	sock_str_flag },
	{ "SO_RCVBUF",			SOL_SOCKET,	SO_RCVBUF,		sock_str_int },
	{ "SO_SNDBUF",			SOL_SOCKET,	SO_SNDBUF,		sock_str_int },
	{ "SO_RCVLOWAT",		SOL_SOCKET,	SO_RCVLOWAT,	sock_str_int },
	{ "SO_SNDLOWAT",		SOL_SOCKET,	SO_SNDLOWAT,	sock_str_int },
	{ "SO_RCVTIMEO",		SOL_SOCKET,	SO_RCVTIMEO,	sock_str_timeval },
	{ "SO_SNDTIMEO",		SOL_SOCKET,	SO_SNDTIMEO,	sock_str_timeval },
	{ "SO_REUSEADDR",		SOL_SOCKET,	SO_REUSEADDR,	sock_str_flag },
#ifdef	SO_REUSEPORT
	{ "SO_REUSEPORT",		SOL_SOCKET,	SO_REUSEPORT,	sock_str_flag },
#else
	{ "SO_REUSEPORT",		0,			0,				NULL },
#endif
	{ "SO_TYPE",			SOL_SOCKET,	SO_TYPE,		sock_str_int },
	// { "SO_USELOOPBACK",		SOL_SOCKET,	SO_USELOOPBACK,	sock_str_flag },
	{ "IP_TOS",				IPPROTO_IP,	IP_TOS,			sock_str_int },
	{ "IP_TTL",				IPPROTO_IP,	IP_TTL,			sock_str_int },
#ifdef	IPV6_DONTFRAG
	{ "IPV6_DONTFRAG",		IPPROTO_IPV6,IPV6_DONTFRAG,	sock_str_flag },
#else
	{ "IPV6_DONTFRAG",		0,			0,				NULL },
#endif
#ifdef	IPV6_UNICAST_HOPS
	{ "IPV6_UNICAST_HOPS",	IPPROTO_IPV6,IPV6_UNICAST_HOPS,sock_str_int },
#else
	{ "IPV6_UNICAST_HOPS",	0,			0,				NULL },
#endif
#ifdef	IPV6_V6ONLY
	{ "IPV6_V6ONLY",		IPPROTO_IPV6,IPV6_V6ONLY,	sock_str_flag },
#else
	{ "IPV6_V6ONLY",		0,			0,				NULL },
#endif
	{ "TCP_MAXSEG",			IPPROTO_TCP,TCP_MAXSEG,		sock_str_int },
	{ "TCP_NODELAY",		IPPROTO_TCP,TCP_NODELAY,	sock_str_flag },
#ifdef	SCTP_AUTOCLOSE
	{ "SCTP_AUTOCLOSE",		IPPROTO_SCTP,SCTP_AUTOCLOSE,sock_str_int },
#else
	{ "SCTP_AUTOCLOSE",		0,			0,				NULL },
#endif
#ifdef	SCTP_MAXBURST
	{ "SCTP_MAXBURST",		IPPROTO_SCTP,SCTP_MAXBURST,	sock_str_int },
#else
	{ "SCTP_MAXBURST",		0,			0,				NULL },
#endif
#ifdef	SCTP_MAXSEG
	{ "SCTP_MAXSEG",		IPPROTO_SCTP,SCTP_MAXSEG,	sock_str_int },
#else
	{ "SCTP_MAXSEG",		0,			0,				NULL },
#endif
#ifdef	SCTP_NODELAY
	{ "SCTP_NODELAY",		IPPROTO_SCTP,SCTP_NODELAY,	sock_str_flag },
#else
	{ "SCTP_NODELAY",		0,			0,				NULL },
#endif
	{ NULL,					0,			0,				NULL }
};


int
main() {
	struct SockOpts *opt;
	int fd = 0;
	socklen_t len;
	union OptVal val;

	for (int i = 0; sock_opts[i].opt_str; i++) {
		opt = &sock_opts[i];
		printf("%s: ", opt->opt_str);

		if (opt->opt_val_str == NULL) {
			printf("(undefined)\n");
		} else {
			switch(opt->opt_level) {
			case SOL_SOCKET:
			case IPPROTO_IP:
			case IPPROTO_TCP:
				fd = socket(AF_INET, SOCK_STREAM, 0);
				assert(fd >= 0);
				break;
#ifdef	IPV6
			case IPPROTO_IPV6:
				fd = socket(AF_INET6, SOCK_STREAM, 0);
				assert(fd >= 0);
				break;
#endif
#ifdef	IPPROTO_SCTP
			case IPPROTO_SCTP:
				fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
				assert(fd >= 0);
				break;
#endif
			default:
				printf("can't create fd for level %d\n", opt->opt_level);
			}

			len = sizeof(val);
			if (getsockopt(fd, opt->opt_level, opt->opt_name, &val, &len) < 0) {
				printf("getsockopt error\n");
			} else {
				printf("default = %s\n", (*opt->opt_val_str)(&val, len));
			}
			close(fd);
		}
	}
}
